19 - Styling

接下来的内容就是styling相关的。

react native里面的样式写法和web端的写法有类似,也有区别。

1、react native里面不写CSS相关代码,而是在js里面写样式。

2、样式的名称采用小驼峰的写法,比如说在web端我们这样写background-color,而在react native里面写成backgroundColor

image-20251019205840922

在react native中有两种写样式的方法,第一种方式是写内联样式,直接在标签上定义style属性。第二种方式是使用react native提供的StyleSheet API,我们经常使用StyleSheet.create方法来编写样式。

image-20251019210137099

image-20251019210328234

20 - StyleSheet API

效果:

image-20251019211501677

一般来说,每一个组件里面的styles都定义在自己的组件里面,但是有一些通用的styles,可以使用export暴露出去,让全局都可以使用。

21 - Multiple Styles

https://reactnative.dev/docs/style

image-20251020093150275

这一节课主要讲解如何为组件设置多种样式,什么意思呢?

我们从上面学习到,可以为组件这样设置样式<View style={styles.container}></View>,但是样式肯定是有一些重复的部分,这些部分就可以设置成单独的对象名,那么怎么在style属性里面设置多个样式对象呢?这节课就是讨论这个问题。

解决办法就是指定style属性的值为数组,像这样子:<View style={[styles.container, styles.box]}></View>

效果:

image-20251020095050064

注意,style属性的值如果是数组,那么里面的顺序是有意义的,如果有重复的样式,后面的样式优先级高一些,后面的样式会覆盖前面的样式。

22 - Box Model

CSS的盒模型在react native中是与web端效果一致的,当然react native提供了一些自己的样式。

image-20251020092453430

尺寸没有单位

react native中的所有尺寸都是没有单位的,表示的意思是与密度无关的像素。但是可以使用百分数来表示。

image-20251020094130611

image-20251020095113480

react native独特的样式

有几个独特的样式要记忆一下:paddingHorizontal,paddingVertical,marginVertical,marginHorizontal。肯定还有很多,但是先记忆这几个。

image-20251020095311114

效果:

image-20251020095824911

注意:这里设置了padding之后,盒子并没有变大,因为默认是boxSizing: border-box。这个值是可以设置的。

border

border: "2px solid blue",web端设置CSS的样式,在react native中不起作用。react native中需要一个一个的来设置。

image-20251020095657790

效果:

image-20251020095831272

borderRadius的独特性

当borderRadius设置到Text组件上时,只在android上起作用,在ios上不起作用。解决办法就是在Text外面包裹View组件,将borderRadius设置到View上面。

当borderRadius设置到View组件上时,在ios和android上都起作用。

image-20251020094842161

效果:

image-20251020094739982

23 - Shadow and Elevation

在web端写box-shadow的时候,我们像下面这样写。但是在react native中,我们要一个一个属性来写。

image-20251020100143173

image-20251020100403080

在第一个盒子上面设置boxShadow,可以看到在ios上生效了,但是在android端没有生效。

image-20251020101404442

怎么解决这个问题呢?使用elevation样式属性来解决。还有一些第三方库来解决这个问题,这个可以做项目的时候找一下。

image-20251020101705162

image-20251020101755155

24 - Style Inheritance

在web端的css中,样式会继承。像下面这个案例,在div上设置字体颜色,里面的p标签的字体会继承color属性。

image-20251020102102557

在react native中,View和Text之间是没有样式继承的,看下面这个案例:

image-20251020102432131

但是Text与Text的嵌套,是有样式继承的。

image-20251020102948719

25 - Layout with Flexbox

在react native中,最核心的布局方式就是使用flexbox。

image-20251020103528408

flexbox里面有两个主要的实体,flex容器和flex子级。

image-20251020104425378

与web端的flex布局不同,react native里面的main轴,是从上到下的。

image-20251020104520550

学习react native里面的flexbox布局也很简单,熟悉它的属性、了解这些属性的作用、用起来就行了。

image-20251020104611906

26 - Code Setup

这节课就是为了讲解flexbox做准备。创建一个Box组件。

引入到app/index.tsx中

效果如下:

image-20251020111827716

27 - Flex

注意:

与web端的flexbox布局不同,react native里面不需要先定义display: flex;,react native默认设置View组件的display属性为flex,所以不需要设置。

image-20251020113349960

这节课讲解flexbox里面的flex属性,这个属性的作用是定义flex items怎么沿着flexDirection填充可用空间。在react native中,flexDirection默认是column。

如果有多个flex items,那么flex的值就是占可用空间的比例。

这里有一个重要的概念,什么是可用空间(available space)?

In the React Native documentation, "available space" refers to the remaining space along the main axis of a flex container that is not occupied by the elements' intrinsic sizes (e.g., their content or fixed dimensions) after the layout is calculated. This space is then distributed among the child elements based on their flex properties.

在 React Native 的文档中,“可用空间”(available space)指的是 Flex 容器沿着主轴方向上,扣除子元素固有尺寸(例如内容或固定尺寸)后剩余的空间。这个空间会根据子元素的 flex 属性按比例分配给它们。

详细解释

image-20251020111945740

我们经常在View组件上使用flex: 1来表示占用整个available的空间。这一点为什么能成立呢?可能是因为最后打包的代码中,app外层还是包裹了一层View的,所以这里直接定义flex:1就可以占用整个available空间了。

效果:

img

设置一下子元素的flex属性,看一下效果:

效果:

img

28 - Flex Direction

image-20251020132940836

image-20251020132844741

在container这个样式里面设置flexDirection,可以看一下效果。

29 - Justify Content

justifyContent是设置在main axis上的对齐方式。

image-20251020133453765

image-20251020133432601

30 - Align Items

alignItems是设置在cross axis上的对齐方式。

image-20251020134104760

image-20251020134122023

31 - Align Self

与alignItems属性类似,alignSelf有5种值。但是与alignItems不同,alignItems是设置到父元素上的,而alignSelf是设置到子元素上的,并且它的值会覆盖父元素上alignItems的值。

image-20251020135242839

image-20251020135442017

alignSelf的默认值是auto,它会继承父元素的alignItems的值,如果父元素没有设置alignItems的值,那么alignItems的默认值就是stretch。所以子元素的alignSelf的值就是stretch。

为什么要说这一点呢?就是为了强调alignSelf的默认值并不是stretch,而是auto。

32 - Flex Wrap

image-20251020140454925

image-20251020140203509

效果:

img

flexWrap我觉得还是蛮重要的,因为无论是H5还是小程序,我都遇到过九宫格的样式,我到现在都没有找到很好的解决办法。搜索了一下,还是使用计算宽度的方式来实现,flexbox布局倒是没有什么。

33 - Align Content

当使用了flexWrap: wrap或者wrap-reverse之后,设置alignContent才能生效。alignContent用来定义cross axis上的布局。

但是之前已经有了alignItems啊,这不是重复了吗?之前已经说了,alignContent只有在设置了flexWrap: wrap或者wrap-reverse之后才会生效,此时如果设置了alignItems是不会生效的,必须使用alignContent来设置。

image-20251020142741940

image-20251020142131175

效果:

img

34 - Gap

image-20251020145621274

image-20251020145650191

gap属性我一直不敢用,老是设置margin、padding来实现。其实gap搞清楚了非常好用,设置gap只对子元素之间起作用,对子元素和父元素之间的关系不造成影响,搞清楚了这一点,就大胆使用。

效果:

img

35 - Flex Basis

如果flexDirection是row,那么设置flexBasis的值表示的就是子元素的width,如果flexDirection是column,那么设置flexBasis的值表示的就是子元素的height。

image-20251020151158417

image-20251020150956321

为什么不直接设置width或者height呢?

原因是设置了flex-basis,表示初始的width或height,同时加上flex:1,可以占据其余的available space。而设置了width或者height之后,然后设置flex:1,会比设置flexBasis的要小一些。原因是available space is distributed propertionally with respect to the flex basis, not with width or height property.

下面是案例说明:

刚开始效果是一样的:

img

Box 3和Box 4都加上flex:1的样式之后,效果就不一样了:

img

36 - Flex Shrink

shrink本意是收缩,flexShrink的意思就是,当子元素的总宽度或总高度超过了父元素的宽度或高度,那么子元素会被压缩,react native中的默认值为0,可以是任意大于等于0的浮点数。0表示不压缩。

image-20251020153638655

image-20251020153513950

https://www.yogalayout.dev/docs/styling/flex-basis-grow-shrink,这个例子可以说明,我设置第二个节点的flexShrink为0,那么被压缩的就是第一个节点。

image-20251020160654053

image-20251020160703933

image-20251020160711839

37 - Flex Grow

grow的本意是生长,flexGrow的意思就是,子元素的总宽度或者总高度没有达到父元素的宽度或高度,那么设置了flexGrow的元素就会变宽或者变高,使得总宽度或总高度达到父元素的宽度或高度。

flexGrow的默认值是0,意味着子元素不会默认变宽或者变高。

image-20251020161130041

image-20251020161422752

将Box 6设置flexGrow:1,看一下效果:

image-20251020161528472

效果:

img

 

那之前不是学习了flex:1也能达到同样的效果吗?二者有说明区别吗?

有的,设置flex:1实际上设置了三个属性:flexGrow: number, flexShrink: 1, flexBasis: 0

image-20251020161951188

38 - Relative and Absolute Layout

react native中的position默认就是relative布局,那么在某个组件上使用position:absolute的时候,是不需要在其父组件上特别设置position:relative的,这一点与web端的position布局不同,要注意。

image-20251020162624993

image-20251020162559094

image-20251020162858146

image-20251020162932410

将Box组件设置为width:100,height:100,,方便展示样式。

img

position: relative

在Box 1和Box 5上设置top和left属性,因为它们默认的position是relative,看一下效果。

img

可以看到position:relative时,设置了left和top,是不影响它的兄弟组件或者父组件的布局的。

position: absolute

将Box 5的position设置为absolute,看一下效果。

img

可以看到,设置为position:absolute之后,具体的位置与其父组件相关,与其兄弟组件的位置无关。

39 - Dynamic User Interfaces

动态用户界面。这节课的意思是说,之前的学习都是基于标准的iphone14和pixel 4这种设备来设置样式的,但是用户的设备可能有很多种类型,同一种设备可能竖着使用,也可能横着使用。

怎么解决这个问题呢?react native提供了一些API来帮助我们,接下来的几节课会讲到。

image-20251021205701038

为了更好的学习下面的API,创建一个新项目,将app/index.tsx改成下面这样:

40 - Dimensions API

效果:

img

然后在terminal里面执行shift + i,切换macos端的ios模拟器,我没有这个,就直接使用老师的截图来说明了。在ipad上面,box太小了,而且字体也太小,看的不是很清楚。

image-20251021210848362

一种解决办法是在width和height上使用百分数,但是字体大小无法使用百分数,而且使用了百分数之后,在ipad上的box比例和iphone上的比例区别还是蛮大的。

这时候就要使用dimensions API,react native提供的Dimensions这个API,可以获取到设备的宽度和高度,里面的get方法有两种值screenwindow,一般使用window,因为window表示app可见的范围。

这个我感觉类似CSS里面的@media媒体查询,要定义一些屏幕的类型。

效果:

image-20251021211444827

41 - Dimensions API Drawback

image-20251021212057809

虽然dimensions可以解决问题,但是它有一个缺点,就是当设备切换横屏、竖屏显示的时候,或者是折叠屏展开、收起的时候,需要额外设置监听事件来重新渲染,这个就比较麻烦了。

为了查看屏幕横屏的情况,需要将app.json里面的orientation的值由portrait改为default,这样横屏就可以响应了。在app/index.tsx里面输出console.log(dimensionWidth, dimensionHeight);

terminal里面的输出:

image-20251021213215450

可以看到只输出了一次,屏幕旋转之后,dimensions的值并不会主动触发更新。可以使用useEffect来监听。

效果:

可以看到,宽度变为了屏幕宽度的70%,字体大小变为了50,说明监听成功了。但是很麻烦。

42 - useWindowDimensions

使用react native提供的useWindowDimensions,替换Dimensions这个API。当设备尺寸或者字体缩放的时候,会自动更新它的值,从而触发页面的重新渲染。

image-20251021214911136

那么案例的代码就可以改为:

效果非常好。

43 - SafeAreaView

现象是,如果不设置paddingTop,那么内容可能被iphone顶部的notch遮住,android端是正常的。于是就可以使用SafeAreaView来代替View组件,避免设置了paddingTop了之后,ios正常了,但是android端顶部多出来了paddingTop的高度。

image-20251021221724670

这个内容被最新的react native弃用了。因为无法在我的手机上复现,所以就不用管了。

官方推荐使用react-native-safe-area-contexthttps://appandflow.github.io/react-native-safe-area-context/,使用方法是在root layout外层包裹一层SafeAreaProvider。

但是expo项目真的没有这种现象,可能是expo已经帮我们处理好了。

44 - Platform Specific Code

当开发跨平台应用时,最大化代码复用性是首要的,但是有时候需要为不同的平台写不同的代码。react native中有以下两种方式:

image-20251113090239988

1、使用Platform module

使用Platform模块。import { Platform } from 'react-native';

案例:当为container设置padding-top时,ios和android表现不一样。

image-20251113090714406

可以看到,ios的间距大一些。

image-20251113090738453

① 使用 Platform.OS

此时可以通过检查 Platform.OS 的值来运行平台特定的代码:

image-20251113090853934

② 使用 Platform.select()

Platform.select() 方法接受一个包含平台名称作为键的对象,并返回当前平台对应的值。这对于处理样式或返回不同组件非常方便。适合更复杂的处理逻辑。

image-20251113091118135

2、使用平台特定的文件扩展名

当平台间的差异很大,需要编写整个组件或模块的平台特定版本时,推荐使用平台特定的文件扩展名。

React Native 的模块解析器会自动检测并加载带有特定平台后缀的文件:

创建自定义button文件:

image-20251113092044258

CustomButton.android.tsx的代码类似,就是样式不一样,这里就不粘贴了。

然后引入使用:

查看效果,在ios和android上的样式不一样:

image-20251113094356093

 

这个我没有做好,我定义了这样的组件:

image-20251113094238694

但是引入import CustomeButton from "../components/CustomButton/CustomButton";会报错:

image-20251113094311809

这个先放在这里,以后有时间回来解决。

45 - Build a Pokemon Card (1/6)

之前我们学习了:

image-20251113094627845

接下来的6节课,我们要结合已经学习的知识,做一个pokemon card的项目。 效果如下:

image-20251113094806155

1、创建项目npx create-expo-app@latest --template ExerciseOne,再运行npm run reset-project,输入n

2、修改app/index.tsx里面的内容:

3、添加图片到assets文件夹里面去,就是pokemon相关的

4、创建组件

5、引入组件

效果:

img

46 - Build a Pokemon Card (2/6)

编辑Card组件的样式:

效果:

img

47 - Build a Pokemon Card (3/6)

将传递过来的props显示到Card组件上:

效果:

img

48 - Build a Pokemon Card (4/6)

编辑Card里面内容的样式:

效果:

img

49 - Build a Pokemon Card (5/6)

继续编辑Card里面内容的样式,主要是type、moves、weakness相关的样式:

效果:

img

50 - Build a Pokemon Card (6/6)

Card组件已经做好了,接下来就是渲染多个组件了。要使用ScrollView组件。

效果: